package com.wxmit.module.system.service.impl;

import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.toolkit.Db;
import com.wxmit.base.util.StringUtils;

import com.wxmit.module.system.domain.SysRole;
import com.wxmit.module.system.domain.SysRoleDept;
import com.wxmit.module.system.domain.SysRoleMenu;
import com.wxmit.module.system.domain.SysUserRole;
import com.wxmit.module.system.mapper.SysRoleDeptMapper;
import com.wxmit.module.system.mapper.SysRoleMapper;
import com.wxmit.module.system.mapper.SysRoleMenuMapper;
import com.wxmit.module.system.mapper.SysUserRoleMapper;
import com.wxmit.module.system.service.ISysRoleService;
import com.wxmit.starter.cache.api.CacheApi;
import com.wxmit.system.base.core.ShushanServiceImpl;
import com.wxmit.system.base.domain.PageDTO;
import com.wxmit.system.domain.SysRoleDTO;
import com.wxmit.system.enums.DataPermissionEnum;
import cn.hutool.core.lang.Assert;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import org.springframework.util.CollectionUtils;
import javax.annotation.PostConstruct;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Description SysRoleServiceImpl
 * @Author huangxiong
 * @Date 2022/7/11 14:49
 * @Version V1.0
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class SysRoleServiceImpl extends ShushanServiceImpl<SysRoleMapper, SysRole> implements ISysRoleService {

    public static final String ROLE_REDIS_KEY = "sys:role";

    private final CacheApi cacheApi;

    private final SysRoleMenuMapper roleMenuMapper;

    private final ThreadPoolTaskExecutor threadPoolTaskExecutor;

    private final SysRoleDeptMapper sysRoleDeptMapper;

    private final SysUserRoleMapper sysUserRoleMapper;

    @PostConstruct
    @Async
    public void refreshRoleCache(){
       List<SysRoleDTO> roleDTOList = baseMapper.selectRoleByPes();
       Map<String,SysRoleDTO> map = new HashMap<String,SysRoleDTO>(roleDTOList.size());
       // 筛选自定义数据权限的角色
        Set<Long> customDpIds = roleDTOList.stream().filter(it -> Objects.equals(DataPermissionEnum.CUSTOM.getCode(), it.getDataScope())).map(SysRoleDTO::getId).collect(Collectors.toSet());
        Map<Long,Set<Long>> customDpRelativeMap = new HashMap<>();
        if(CollUtil.isNotEmpty(customDpIds)){
            List<SysRoleDTO> customDpRelatives = baseMapper.selectCustomDpDetailById(customDpIds);
            customDpRelatives.forEach(it->customDpRelativeMap.put(it.getId(),it.getDeptIds()));
        }
        roleDTOList.forEach(it->{
            Set<Long> deptIds = customDpRelativeMap.get(it.getId());
            it.setDeptIds(deptIds);
            map.put(it.getRoleKey(),it);
       });
       map.forEach((k,v)->{
           cacheApi.set(ROLE_REDIS_KEY +k,v);
        });
    }

    @Override
    public List<SysRoleDTO> getRoleCacheValue(List<String> roleKeys){
        List<SysRoleDTO> collect = doGetRoleCacheValue(roleKeys);
        // 避免缓存失效，为空时 再刷新一次缓存。
        if(CollectionUtils.isEmpty(collect)&& CollUtil.isNotEmpty(roleKeys)){
            refreshRoleCache();
            collect = doGetRoleCacheValue(roleKeys);
        }
        return collect;
    }

    @Override
    public SysRole getInfo(Long id) {
        SysRole sysRole = getById(id);
        LambdaQueryChainWrapper<SysRoleDept> rdLambdaQuery = Db.lambdaQuery(SysRoleDept.class);
        List<SysRoleDept> sysRoleDepts = rdLambdaQuery.eq(SysRoleDept::getRoleId, id).list();
        sysRole.setDeptIds(sysRoleDepts.stream().map(SysRoleDept::getDeptId).collect(Collectors.toList()));
        List<Long> menuIds = roleMenuMapper.selectUnCheckStrictlyMenuListByRoleId(id);
        sysRole.setMenuIds(menuIds);
        return sysRole;
    }

    @Override
    public List<SysRole> listSelect() {
        return lambdaQuery().select(SysRole::getId,SysRole::getRoleKey,SysRole::getRoleName).list();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean save(SysRole entity) {
        checkRoleKeyUnion(entity.getRoleKey(),null);
        boolean result = super.save(entity);
        refreshTableRelation(entity,false);
        doRefreshRoleCache(result);
        return result;
    }

    @Override
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    public boolean updateById(SysRole entity) {
        checkAdmin(entity.getId());
        checkRoleKeyUnion(entity.getRoleKey(),entity.getId());
        boolean result = super.updateById(entity);
        refreshTableRelation(entity,true);
        doRefreshRoleCache(result);
        return result;
    }

    @Override
    public boolean removeByIds(Collection<?> list) {
        list.forEach(it->checkAdmin((Long)it));
        boolean result = super.removeBatchByIds(list);
        doRefreshRoleCache(result);
        return result;
    }

    @Override
    public List<Long> getRoleIdsByUserId(Long userId) {
        List<SysUserRole> sysUserRoles = sysUserRoleMapper.selectList(new QueryWrapper<SysUserRole>().lambda().eq(SysUserRole::getUserId, userId));
        if(CollectionUtils.isEmpty(sysUserRoles))return Collections.emptyList();
        return sysUserRoles.stream().map(SysUserRole::getRoleId).collect(Collectors.toList());
    }



    @Override
    public void updateStatus(Long id, String status) {
       updateById(SysRole.builder().id(id).status(status).build());
    }

    @Override
    protected void processPageDTO(PageDTO<SysRole> pageDTO, LambdaQueryChainWrapper<SysRole> lambdaQuery) {
        SysRole searchData = pageDTO.getSearchData();
        if(Objects.isNull(searchData)){
            return;
        }
        lambdaQuery.likeRight(!StringUtils.isBlank(searchData.getRoleName()),SysRole::getRoleName,searchData.getRoleName());
    }

    private void refreshTableRelation(SysRole entity,Boolean updateFlag){
        // 数据权限
        if(Objects.equals(entity.getDataScope(), DataPermissionEnum.CUSTOM.getCode())){
            Assert.notEmpty(entity.getDeptIds(),"自定义数据权限，部门不能为空！");
            if(updateFlag){
                Db.remove(new QueryWrapper<SysRoleDept>().setEntity(new SysRoleDept()).lambda().eq(SysRoleDept::getRoleId,entity.getId()));
            }
            List<SysRoleDept> roleDepts = entity.getDeptIds().stream().map(it -> new SysRoleDept(entity.getId(),it)).collect(Collectors.toList());
            Db.saveBatch(roleDepts);
        }
        // 菜单关系
        if(!CollectionUtils.isEmpty(entity.getMenuIds())){
            if(updateFlag){
                roleMenuMapper.delete(new QueryWrapper<SysRoleMenu>().lambda().eq(SysRoleMenu::getRoleId,entity.getId()));
            }
            List<SysRoleMenu> roleMenus = entity.getMenuIds().stream().map(it -> new SysRoleMenu(entity.getId(),it)).collect(Collectors.toList());
            roleMenuMapper.insertBatch(roleMenus);
        }
    }

    private void checkRoleKeyUnion(String roleKey,Long id){
        LambdaQueryChainWrapper<SysRole> lambdaQuery = lambdaQuery();
        lambdaQuery.eq(SysRole::getRoleKey,roleKey);
        if(Objects.nonNull(id)){
            lambdaQuery.ne(SysRole::getId,id);
        }
        Long count = lambdaQuery.count();
        Assert.isTrue(count.compareTo(0L) == 0,"当前角色标识符已被占用！");
    }

    private void checkAdmin(Long roleId){
        Assert.isTrue(!Objects.equals(roleId,1L),"管理员角色无法修改！");
    }

    private void doRefreshRoleCache(Boolean enable){
        if(enable){
            //异步
            threadPoolTaskExecutor.execute(this::refreshRoleCache);
        }
    }

    private List<SysRoleDTO> doGetRoleCacheValue(List<String> roleKeys){
        if(CollUtil.isEmpty(roleKeys)){
            return Collections.emptyList();
        }
        List<SysRoleDTO> result = new ArrayList<>(roleKeys.size());
        roleKeys.forEach(it ->  {
            Optional<SysRoleDTO> sysRoleDTOOpt = cacheApi.<SysRoleDTO>get(ROLE_REDIS_KEY + it);
            sysRoleDTOOpt.ifPresent(result::add);
        });
        return result;
    }
}
